home *** CD-ROM | disk | FTP | other *** search
Text File | 1989-11-26 | 40.8 KB | 1,127 lines |
- Newsgroups: comp.sources.misc
- subject: v09i009: exec profiler for MSDOS
- from: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
- Reply-To: bl@infovox.se (Bj|rn Larsson)
-
- Posting-number: Volume 9, Issue 9
- Submitted-by: bl@infovox.se (Bj|rn Larsson)
- Archive-name: proft.ms
-
- This is an execution time profiler for DOS and TurboC/MicroSoft C. Tested
- under both the compilers under PCDOS 3.30.
-
- # ----------------------------- cut here -----------------------------
- #! /bin/sh
- # This is a shell archive. Remove anything before the `cut' line,
- # then unpack by saving it into a file and typing `sh file'. The
- # archive ends by exit(0), so don't worry about trailing junk.
- #
- # Contents:
- #
- # makefile
- # makefile.msc
- # profile.c
- # profile.man
- # README
- #
- # Wrapped by USER@MS-DOS --- Tue Nov 21 21:23:30 1989
- #
- PATH=/bin:/usr/bin:/usr/ucb ; export PATH
- if test -f makefile -a "${1}" != "-c" ; then
- echo Will not over-write existing file \"makefile\"
- else
- echo Extracting - \"makefile\"
- sed "s/^X//" >makefile <<'END_OF_makefile'
- X#################################################################
- X# Makefile for PROFILE, Turbo-C 2.0 version. #
- X# Revised: 890904 #
- X#################################################################
- X
- XCC=tcc
- XCFLAGS=-ml -DTRC_2_0=1 -w -w-pro -w-use -w-par -c
- XLINK=tlink
- XLFLAGS=/m/c
- XLIBDIR=\trc\lib
- X
- Xdefault: proft.exe
- X
- Xproft.exe: proft.obj
- X $(LINK) $(LFLAGS) $(LIBDIR)\c0l.obj proft.obj,proft.x,\
- X proft.map,$(LIBDIR)\emu.lib $(LIBDIR)\mathl.lib $(LIBDIR)\cl.lib
- X exepack proft.x proft.exe
- X rm -il proft.x
- X
- Xproft.obj: profile.c
- X $(CC) $(CFLAGS) -oproft.obj profile.c
- END_OF_makefile
- if test 572 -ne `wc -c <makefile`; then
- echo \"makefile\" unpacked with wrong size!
- fi
- # end of overwriting check
- fi
- if test -f makefile.msc -a "${1}" != "-c" ; then
- echo Will not over-write existing file \"makefile.msc\"
- else
- echo Extracting - \"makefile.msc\"
- sed "s/^X//" >makefile.msc <<'END_OF_makefile.msc'
- X#################################################################
- X# Makefile for PROFILE, MicroSoft C 5.1 version. #
- X# Revised: 890902 #
- X#################################################################
- X
- XCC=cl
- XCFLAGS=-Ml -DMSC_5_1=1 -W2 -c
- XLINK=link
- XLFLAGS=/MAP /EXEPACK
- XLIBDIR=\msc\lib
- X
- Xdefault: profm.exe
- X
- Xprofm.exe: profm.obj
- X $(LINK) $(LFLAGS) profm.obj,profm.exe,profm.map;
- X
- Xprofm.obj: profile.c
- X $(CC) $(CFLAGS) -Foprofm.obj profile.c
- END_OF_makefile.msc
- if test 447 -ne `wc -c <makefile.msc`; then
- echo \"makefile.msc\" unpacked with wrong size!
- fi
- # end of overwriting check
- fi
- if test -f profile.c -a "${1}" != "-c" ; then
- echo Will not over-write existing file \"profile.c\"
- else
- echo Extracting - \"profile.c\"
- sed "s/^X//" >profile.c <<'END_OF_profile.c'
- X/****************************************************************/
- X/* PROFILE.C */
- X/* */
- X/* Execution time profiler. Reads an executable and it's link */
- X/* map and produces an output file with hit counts for all the */
- X/* functions listed in the map file. Handles (by option selec- */
- X/* tion) both MicroSoft LINK and Borland TLINK map files. */
- X/* PROF only profiles '.EXE' files. */
- X/****************************************************************/
- X/* Command line: */
- X/* */
- X/* prof [-adin0$?] [-#<n>] [-m<mapfile>] [-o<outfile>] <cmd> */
- X/* */
- X/* -d Include DOS areas (DOS and BIOSes) in the profiling. */
- X/* -a Sort output table by address, not by frequency. */
- X/* -i Include intrinsic functions (name starting with '__, */
- X/* or containing '@' or '$')' in the profiling. */
- X/* -n Sort output table by name, not by frequency. */
- X/* -0 List also functions that got no hits. */
- X/* -#<n> */
- X/* Execute the profiled command <n> times to increase */
- X/* statistical confidence. */
- X/* -? Report the address of the own PSP and main() function. */
- X/* -$ Report the address of the top DOS aloccation. */
- X/* -m<mapfile> */
- X/* Read link map from file <mapfile> (default <cmd>.MAP). */
- X/* -o<outfile> */
- X/* Output profile table in file <outfile> (default */
- X/* <cmd.PRF>). */
- X/* <cmd> */
- X/* The normal command line for the profiled command. */
- X/****************************************************************/
- X/* Exit codes delivered by prof: */
- X/* */
- X/* 0: No error. */
- X/* 1: Illegal command line arguments. */
- X/* 2: Cannot open command binary. */
- X/* 3: Cannot open linker map file. */
- X/* 4: Cannot open output file. */
- X/* 5: Invalid map file format. */
- X/* 6: Insufficient memory. */
- X/* 7: Invalid EXE file format. */
- X/* 8: EXEC error */
- X/* 9: Program too fast - no hits. */
- X/****************************************************************/
- X/* Revised: */
- X/* 1.02: Doesn't show functions that were not hit, thus */
- X/* reducing the amount of output data: 890909 */
- X/* 1.01: Attempts to make load address more certain: 890906 */
- X/* 1.00: Functional: 890904 */
- X/****************************************************************/
- X#include <stdio.h>
- X#include <stdlib.h>
- X#include <string.h>
- X#include <ctype.h>
- X#include <io.h>
- X#include <fcntl.h>
- X#include <sys/types.h>
- X#include <sys/stat.h>
- X#include <process.h>
- X#include <dos.h>
- X#include <errno.h>
- X#if TRC_2_0
- X#include <alloc.h>
- X#endif
- X#if MSC_5_1
- X#include <malloc.h>
- X#include <signal.h>
- X#endif
- X
- X/****************************************************************/
- X/* Adjuct constant for file load addresses. */
- X/* THIS IS COMPILER SPECIFIC AND MUST BE FOUND EMPIRICALLY! */
- X/****************************************************************/
- X
- X#if TRC_2_0
- X#define ADJ_CONST 0L
- X#endif
- X#if MSC_5_1
- X#define ADJ_CONST 0x160L
- X#endif
- X
- X/* Compiler-specific #defines for DOS access functions */
- X
- X#if TRC_2_0
- X#define ENABLE() enable()
- X#define DISABLE() disable()
- X#define ALLMEM(s,p,r) (r = allocmem(0xffff,p))
- X#define ALLMEMERR(s,p) (allocmem(s,p) != -1)
- X#define FREEMEM(p) freemem(p)
- X#define CTRLBREAK(f) ctrlbrk(f)
- X#define SETVECT(i,f) setvect(i,f)
- X#define GETVECT(i) getvect(i)
- X#endif
- X#if MSC_5_1
- X#define ENABLE() _enable()
- X#define DISABLE() _disable()
- X#define ALLMEM(s,p,r) (_dos_allocmem(0xffff,&r))
- X#define ALLMEMERR(s,p) (_dos_allocmem(s,p) != 0)
- X#define FREEMEM(p) _dos_freemem(p)
- X#define CTRLBREAK(f) signal(SIGINT,f)
- X#define SETVECT(i,f) _dos_setvect(i,f)
- X#define GETVECT(i) _dos_getvect(i)
- X#endif
- X
- X#define TMRINT 8 /* Timer hardware interrupt */
- X#define MAXFUNC 2000 /* Max 2000 functions... */
- X#define CMDSIZ 130 /* Cmd's command line size */
- X#define FNSIZ 130 /* Max file name size */
- X#define LNSIZ 130 /* Max map file line length */
- X#define STSIZ 35 /* Size of small strings */
- X
- X#define ABSTYP 1 /* Table entry is absolute */
- X#define USRTYP 2 /* Table entry is USR func */
- X#define INRTYP 4 /* Table entry is INR type */
- X#define DOSTYP 8 /* Table entry is DOS type */
- X
- Xtypedef struct /* Entry in funcion table */
- X {
- X unsigned long addr; /* Function start address */
- X unsigned long hits; /* Hit count */
- X char *name; /* Function name */
- X char typ; /* Function properties */
- X char dummy; /* For word-aligning */
- X } fdesc; /* Function descriptor */
- X
- Xstatic char exename[FNSIZ] = {0}; /* Prof'ed command's binary */
- Xstatic char mapname[FNSIZ] = {0}; /* Prof'ed command's map */
- Xstatic char outname[FNSIZ] = {0}; /* Prof output table file */
- X
- Xstatic FILE *file = NULL; /* For read/write of files */
- Xstatic struct stat statinfo; /* Stat() buffer */
- Xstatic fdesc *descs; /* Start of desc table */
- Xstatic int exe_count = 1; /* # times to exec command */
- Xstatic int nfuncs; /* Number of functions */
- Xstatic char name_ordr = 0; /* If sorting address-wise */
- Xstatic char addr_ordr = 0; /* If sorting adress.-wise */
- Xstatic char tell_psp = 0; /* Tell load PSP address */
- Xstatic char list_nulls = 0; /* IF listing no-hit funcs */
- Xstatic char wrt_msk = USRTYP; /* Default only user funcs */
- Xstatic double tot_hits = 0.0; /* Total function hits */
- Xstatic double dsp_hits = 0.0; /* Total displayed hits */
- Xstatic double usr_hits = 0.0; /* Total user pgm hits */
- Xstatic char **cmdline; /* Command line array ptr */
- Xstatic unsigned freemem_pg = 0; /* At beg of free memory */
- Xstatic unsigned load_psp = 0; /* Paragr of load area */
- X
- Xstatic void (interrupt *old_tmr_handler)() = NULL; /* Original handler */
- X
- Xstatic char *msgs[] =
- X {
- X "profile: ", /* 0 */
- X "Illegal command line option: `-%s'\n", /* 1 */
- X "No command specified for profiling\n", /* 2 */
- X "Cannot find executable `%s'\n", /* 3 */
- X "`%s' is a directory\n", /* 4 */
- X "Cannot find linker map file `%s'\n", /* 5 */
- X "Output file `%s' is write-protected\n", /* 6 */
- X "Invalid map file format in `%s'", /* 7 */
- X "Insufficient memory for function tables\n", /* 8 */
- X "Not enough function table slots\n", /* 9 */
- X "Cannot open output file `%s'\n", /* 10 */
- X "Insufficient memory to execute command", /* 11 */
- X "Error executing command %s\n", /* 12 */
- X "Invalid EXE file format in `%s'\n", /* 13 */
- X "@(#)profile.c v.1.02 - 890909", /* 14 */
- X "Program ran too fast - no hits!\n" /* 15 */
- X } ; /* msgs */
- X
- Xstatic void get_cmdline(); /* Interpret command line */
- Xstatic void set_filenames(); /* Fix definitive filenames */
- Xstatic void check_files(); /* Check files are OK */
- Xstatic void read_map(); /* Read map, build tables */
- Xstatic void add_func(); /* add function to table */
- Xstatic int blankline(); /* Check if line is blank */
- Xstatic void adjust(); /* Set correct func addrs */
- Xstatic void profile(); /* Do the actual profiling */
- Xstatic void write_result(); /* Output result */
- Xstatic int fadr_comp(); /* Compare func addresses */
- Xstatic int fhit_comp(); /* Compare func hitcounts */
- Xstatic int fnam_comp(); /* Compare func names */
- Xstatic void usage(); /* Help routine */
- Xstatic void error(); /* Error/Abort routine */
- X
- Xstatic int brk_handler(); /* SIGINT handler */
- Xstatic void interrupt tmr_handler(); /* Timer interrupt handler */
- X
- X/****************************************************************/
- X
- Xvoid main(narg, args)
- X int narg;
- X char *args[];
- X {
- X CTRLBREAK(brk_handler);
- X get_cmdline(narg, args);
- X set_filenames();
- X check_files();
- X read_map();
- X adjust();
- X profile();
- X write_result();
- X error(0,"");
- X } /* main */
- X
- X/****************************************************************/
- X/* Get_cmdline() extracts all switches etc, gets the name of */
- X/* the command to be profiled, and sets up file names. It also */
- X/* builds the command line for the command to be profiled. */
- X/****************************************************************/
- X
- Xstatic void get_cmdline(narg, args)
- X int narg;
- X char *args[];
- X {
- X int i = 1;
- X char *p;
- X unsigned long m;
- X
- X while ((i < narg) && (*args[i] == '-')) /* Extract switches */
- X {
- X args[i]++;
- X while (*args[i]) /* Get switches */
- X {
- X switch (*args[i])
- X {
- X case '#': if (sscanf(args[i]+1, "%d", &exe_count) != 1)
- X error(1, msgs[1], args[i]);
- X args[i] = " ";
- X break;
- X case '?': printf("Actual PSP is at absolute address 0x%05x0\n",
- X _psp);
- X p = (char *) main; /* Trick for MSC */
- X m = FP_SEG(p);
- X m <<= 4;
- X m += FP_OFF(p);
- X printf("Main() fnc is at absolute address 0x%06lx\n",
- X m);
- X error(0,"");
- X case '$': tell_psp = 1; /* Tell DOS alloc address */
- X break;
- X case 'a': name_ordr = 0; /* Sort table by freq */
- X addr_ordr = 1;
- X break;
- X case 'd': wrt_msk |= DOSTYP; /* Include DOS areas */
- X break;
- X case 'i': wrt_msk |= INRTYP; /* Include intrinsic funcs */
- X break;
- X case 'n': name_ordr = 1; /* Sort table by name */
- X addr_ordr = 0;
- X break;
- X case '0': list_nulls = 1; /* List no-hit funcs */
- X break;
- X case 'm': strcpy(mapname, args[i]+1); /* Map file name */
- X args[i] = " ";
- X break;
- X case 'o': strcpy(outname, args[i]+1); /* Output table file name */
- X args[i] = " ";
- X break;
- X default: error(1, msgs[1], args[i]);
- X } /* switch */
- X args[i]++;
- X } /* while */
- X i++;
- X } /* while */
- X
- X if (i >= narg) /* Check there is a command */
- X error(1, msgs[2]);
- X strcpy(exename,args[i]);
- X cmdline = args + i;
- X } /* get_cmdline */
- X
- X/****************************************************************/
- X/* Set_filenames() adjust names of needed file names based on */
- X/* defaults and options. */
- X/****************************************************************/
- X
- Xstatic void set_filenames()
- X {
- X if (!mapname[0]) /* Set default mapfile name */
- X {
- X strcpy(mapname, exename);
- X strcat(mapname, ".map");
- X } /* if */
- X if (!outname[0]) /* Set default outfile name */
- X {
- X strcpy(outname, exename);
- X strcat(outname, ".prf");
- X } /* if */
- X strcat(exename,".exe"); /* It's an 'EXE' file */
- X } /* set_filenames */
- X
- X/****************************************************************/
- X/* Check_files() checks that all files are available, readable */
- X/* and writeable as required. */
- X/****************************************************************/
- X
- Xstatic void check_files()
- X {
- X if (stat(exename, &statinfo)) /* Check executable exists */
- X error(2, msgs[3], exename);
- X if (statinfo.st_mode & S_IFDIR)
- X error(2, msgs[4], exename);
- X if (stat(mapname, &statinfo)) /* Check mapfile exists */
- X error(3, msgs[5], mapname);
- X if (statinfo.st_mode & S_IFDIR)
- X error(3, msgs[4], mapname);
- X if (stat(outname, &statinfo)) /* Check outfile writeable */
- X return;
- X if (statinfo.st_mode & S_IFDIR)
- X error(4, msgs[4], outname);
- X if (!(statinfo.st_mode & S_IWRITE))
- X error(4, msgs[6], outname);
- X } /* check_files */
- X
- X/****************************************************************/
- X/* Read_map() reads the map file into memory and builds the */
- X/* linked list of entries. */
- X/****************************************************************/
- X
- Xstatic void read_map()
- X {
- X char line[LNSIZ+1];
- X char str1[STSIZ],str2[STSIZ],str3[STSIZ],str4[STSIZ];
- X fdesc *p;
- X long ofs, seg;
- X
- X if ((p = descs = calloc(MAXFUNC,sizeof(fdesc))) == NULL)
- X error(6, msgs[8]);
- X
- X if ((file = fopen(mapname,"r")) == NULL) /* 'Impossible' */
- X error(3, msgs[5], mapname);
- X
- X while (fgets(line, LNSIZ, file) != NULL) /* Find 'Add Pub by Val' */
- X {
- X if (
- X (sscanf(line, " %s %s %s %s ", str1, str2, str3, str4) == 4)
- X &&
- X (stricmp(str1, "Address") == 0)
- X &&
- X (stricmp(str2, "Publics") == 0)
- X &&
- X (strcmp(str3, "by") == 0)
- X &&
- X (strcmp(str4, "Value") == 0)
- X )
- X break;
- X } /* while */
- X if (feof(file))
- X error(5, msgs[7], mapname);
- X
- X while (fgets(line, LNSIZ, file) != NULL) /* Find Non-blank line */
- X if (!blankline(line))
- X break;
- X if (feof(file))
- X error(5, msgs[7], mapname);
- X
- X add_func(p++, "Low Mem", 0l, ABSTYP|DOSTYP); /* Make entry for low mem */
- X add_func(p++, "DOS", 0x400l, ABSTYP|DOSTYP); /* Make entry for low mem */
- X seg = _psp; /* Get profiler's psp */
- X add_func(p++,"Profiler",seg<<4,ABSTYP|DOSTYP);/* Make entry for prof */
- X
- X nfuncs = 2;
- X do /* Process and read another */
- X {
- X if (blankline(line)) /* Blank line end of data */
- X break;
- X if (sscanf(line, " %lx:%lx Abs %s ", &seg, &ofs, str1) != 3)
- X if (sscanf(line, " %lx:%lx %s ", &seg, &ofs, str1) != 3)
- X error(5, msgs[7], mapname);
- X if (str1[0] == '_') /* Play with '_' for */
- X str1[0] = 1; /* alpha sorting */
- X if (str1[1] == '_') /* This is converted back */
- X str1[1] = 0x7f; /* on output */
- X
- X if ( /* Intrinsic function */
- X ((str1[0] == 1) && (str1[1] == 0x7f)) /* with '__' */
- X ||
- X (strchr(str1,'@') != NULL) /* or with '@' */
- X ||
- X (strchr(str1,'$') != NULL) /* or with '$' */
- X )
- X add_func(p++, str1, (seg << 4) + ofs, INRTYP);/* Make entry */
- X else /* User function */
- X add_func(p++, str1, (seg << 4) + ofs, USRTYP);/* Make entry */
- X nfuncs++;
- X if (nfuncs > (MAXFUNC - 10))
- X error(6, msgs[9]);
- X }
- X while (fgets(line, LNSIZ, file) != NULL);
- X
- X add_func(p++,"EGA BIOS", 0xc0000l, ABSTYP|DOSTYP);
- X add_func(p++,"Fixed Disk BIOS", 0xc8000l, ABSTYP|DOSTYP);
- X add_func(p++,"System ROM", 0xf0000l, ABSTYP|DOSTYP);
- X add_func(p++,"System BIOS", 0xfe000l, ABSTYP|DOSTYP);
- X nfuncs += 4;
- X
- X fclose(file);
- X file = (FILE *) NULL;
- X } /* read_map */
- X
- X/****************************************************************/
- X/* Add_func() adds a function to the function table. */
- X/****************************************************************/
- X
- Xstatic void add_func(p, nam, addr, typ)
- X fdesc *p;
- X char *nam;
- X long addr;
- X char typ;
- X {
- X p->addr = addr;
- X p->hits = 0l;
- X if ((p->name = calloc(1,strlen(nam)+1)) == NULL)
- X error(6, msgs[8]);
- X strcpy(p->name, nam);
- X p->typ = typ;
- X } /* add_func */
- X
- X/****************************************************************/
- X/* Blankline() returns 1 if the passed line is entirely blank. */
- X/****************************************************************/
- X
- Xstatic int blankline(s)
- X char *s;
- X {
- X while (*s)
- X {
- X if (!isspace(*s))
- X return(0);
- X s++;
- X } /* while */
- X return(1);
- X } /* blankline */
- X
- X/****************************************************************/
- X/* Adjust() finds out where in memory the executable will be */
- X/* loaded, and adjust function addresses accordingly. Does this */
- X/* By allocating first a hole, then sys memory 1, and then sys */
- X/* memory 2. Sys memory 2 indicates first free address, and is */
- X/* immediately re-freed. The hole is also freed to function as */
- X/* work space for functions that will run before the actual */
- X/* execution of the profiled program. THIS IS TRICKY AND COM- */
- X/* PILER DEPENDENT... */
- X/****************************************************************/
- X
- Xstatic void adjust()
- X {
- X char *hole;
- X long adj;
- X int i;
- X int maxsz;
- X
- X if ((hole = malloc(0x800)) == NULL) /* Fix workspace for others */
- X error(6, msgs[11]);
- X if (ALLMEMERR(0x20, &freemem_pg)) /* Grab small mem over it */
- X error(6, msgs[11]);
- X ALLMEM(0xffff, &load_psp, maxsz); /* See what max space is */
- X if (ALLMEMERR(maxsz, &load_psp)) /* Grab it to know address */
- X error(6, msgs[11]);
- X free (hole); /* Make workspace available */
- X
- X adj = load_psp;
- X adj <<= 4;
- X adj += 0x100 + ADJ_CONST;
- X
- X if (tell_psp) /* If display free start */
- X printf("Expecting PSP at absolute address 0x%06lx\n", adj-0x100L);
- X
- X for (i = 0; i < nfuncs; i++) /* Add adj to func addr:s */
- X if (!((descs + i)->typ & ABSTYP)) /* Only relocatable ones */
- X (descs + i)->addr += adj;
- X
- X qsort(descs,nfuncs,sizeof(fdesc),fadr_comp); /* Sort in address order */
- X } /* adjust */
- X
- X/****************************************************************/
- X/* Profile() does the profiling. It finds out where the pro- */
- X/* filed command will be loaded, adjusts function addresses */
- X/* accordingly, starts timer interrupts, and executes the com- */
- X/* mand as a subshell. */
- X/****************************************************************/
- X
- Xstatic void profile()
- X {
- X int i = 0;
- X
- X old_tmr_handler = GETVECT(TMRINT); /* Save old int vector */
- X SETVECT(TMRINT,tmr_handler); /* Start profiling */
- X DISABLE();
- X FREEMEM(load_psp); /* Free the load area */
- X load_psp = 0; /* To not free it at exit */
- X ENABLE();
- X while(i++ < exe_count)
- X {
- X fprintf(stderr, "%-3d ----- Executing %s\n", i, *cmdline);
- X if (spawnv(P_WAIT, *cmdline, cmdline) == -1)
- X {
- X switch(errno)
- X {
- X case ENOEXEC: error(7,msgs[13], exename);
- X break;
- X case ENOMEM: error(6,msgs[11]);
- X break;
- X default: error(8,msgs[12]);
- X } /* switch */
- X } /* if */
- X } /* switch */
- X
- X SETVECT(TMRINT,old_tmr_handler);
- X (char *) old_tmr_handler = NULL;
- X
- X DISABLE();
- X FREEMEM(freemem_pg);
- X freemem_pg = 0;
- X ENABLE();
- X fprintf(stderr, "--------- Executing completed\n");
- X } /* profile */
- X
- X/****************************************************************/
- X/* Write_result() sorts the data, computes profiling percen- */
- X/* tages, and write out the resulting list. */
- X/****************************************************************/
- X
- Xstatic void write_result()
- X {
- X int i;
- X
- X if (name_ordr) /* Sort table by name? */
- X qsort(descs,nfuncs,sizeof(fdesc),fnam_comp);/* Sort in name order */
- X else
- X if (!addr_ordr)
- X qsort(descs,nfuncs,sizeof(fdesc),fhit_comp);/* Sort in freq order */
- X
- X if ((file = fopen(outname,"w")) == NULL) /* Possible if command did */
- X error(4, msgs[10], outname); /* something like chmod -w */
- X
- X for (i = 0; i < nfuncs; i++) /* Add up total hit counts */
- X {
- X tot_hits += (double) ((descs + i)->hits); /* Add upp total hits */
- X if ((descs + i)->typ & wrt_msk)
- X dsp_hits += (double) ((descs + i)->hits); /* Add upp displayed hits */
- X if ((descs + i)->typ & USRTYP)
- X usr_hits += (double) ((descs + i)->hits); /* Add up user hits */
- X } /* for */
- X if (tot_hits == 0.0) /* Avoid div by 0.0 */
- X tot_hits = 1.0;
- X if (dsp_hits == 0.0) /* Avoid div by 0.0 */
- X {
- X if (!tell_psp)
- X error(9,msgs[15]);
- X else
- X dsp_hits = 1.0;
- X } /* if */
- X if (usr_hits == 0.0) /* Avoid div by 0.0 */
- X usr_hits = 1.0;
- X
- X fprintf(file, "Function name Addr Total Disp User\n\n");
- X for (i = 0; i < nfuncs; i++)
- X {
- X if (((descs + i)->hits == 0) && /* Don't show 0 hit funcs */
- X !(list_nulls || tell_psp))
- X continue;
- X if (wrt_msk & (descs +i)->typ)
- X {
- X if ((descs + i)->name[0] == 1) /* Reconvert fixes done */
- X (descs + i)->name[0] = '_'; /* when reading the map */
- X if ((descs + i)->name[1] == 0x7f)
- X (descs + i)->name[1] = '_';
- X fprintf(file, "%-20s %05lx %6.2f %6.2f",
- X (descs + i)->name, (descs + i)->addr,
- X 100.0 * ((descs + i)->hits / tot_hits),
- X 100.0 * ((descs + i)->hits / dsp_hits));
- X if ((descs + i)->typ & USRTYP) /* Only usrfuncs get col 3 */
- X fprintf(file," %6.2f", 100.0 * ((descs + i)->hits / usr_hits));
- X fprintf(file,"\n");
- X } /* if */
- X } /* for */
- X fprintf(file, "\nStatistics based on %6.0f hits\n", tot_hits);
- X fclose(file);
- X file = (FILE *) NULL;
- X } /* write_result */
- X
- X/****************************************************************/
- X/* Fadr_comp() compares addresses of two functions. If address */
- X/* values are the same, name decides. */
- X/****************************************************************/
- X
- Xstatic int fadr_comp(f1, f2)
- X fdesc *f1, *f2;
- X {
- X if (f1->addr > f2->addr)
- X return(1);
- X if (f1->addr < f2->addr)
- X return(-1);
- X return(fnam_comp(f1,f2));
- X } /* fadr_comp */
- X
- X/****************************************************************/
- X/* Fhit_comp() compares hit counts of two function table */
- X/* entries. If counts are the same, name decides. */
- X/****************************************************************/
- X
- Xstatic int fhit_comp(f1, f2)
- X fdesc *f1, *f2;
- X {
- X if (f1->hits > f2->hits)
- X return(-1);
- X if (f1->hits < f2->hits)
- X return(1);
- X return(fnam_comp(f1,f2));
- X } /* fhit_comp */
- X
- X/****************************************************************/
- X/* Fnam_comp() compares names of two function table entries. */
- X/****************************************************************/
- X
- Xstatic int fnam_comp(f1, f2)
- X fdesc *f1, *f2;
- X {
- X return (stricmp(f1->name, f2->name));
- X } /* fnam_comp */
- X
- X/****************************************************************/
- X/* Usage() displays a usage menu. */
- X/****************************************************************/
- X
- Xstatic void usage()
- X {
- X fprintf(stderr,
- X "Usage: profile [-adin0] [-#<n>] [-m<map>] [-o<out>] <cmd> [<args>...]\n");
- X fprintf(stderr,
- X " -a Sort output by adress, not by frequency\n");
- X fprintf(stderr,
- X " -d Include DOS and BIOS areas in profiling\n");
- X fprintf(stderr,
- X " -i Include intrinsic functions (starting with `__', or\n");
- X fprintf(stderr,
- X " containing the characters `@' or '$') in profiling\n");
- X fprintf(stderr,
- X " -n Sort output by name, not by frequency\n");
- X fprintf(stderr,
- X " -0 List also functions that had zero frequency\n");
- X fprintf(stderr,
- X " -#<n> Execute profiled command <n> times (default 1)\n");
- X fprintf(stderr,
- X " -m<map> Use file <map> as linker map info (default <cmd>.MAP)\n");
- X fprintf(stderr,
- X " -o<out> Put output result in file <out> (default <cmd>.PRF)\n");
- X fprintf(stderr,
- X " <cmd> Name of command to be profiled (including path)\n");
- X fprintf(stderr,
- X " <args> Optional arguments to <cmd>\n");
- X } /* usage */
- X
- X/****************************************************************/
- X/* Error() */
- X/* */
- X/* Print an error diagnosis and exit. */
- X/****************************************************************/
- X/*VARARGS*/
- Xstatic void error(ercode, ermsg, s1, s2, s3, s4)
- X int ercode;
- X char *ermsg;
- X char *s1, *s2, *s3, *s4;
- X {
- X if ((char *) old_tmr_handler != NULL)
- X SETVECT(TMRINT,old_tmr_handler);
- X if (freemem_pg)
- X FREEMEM(freemem_pg);
- X if (load_psp)
- X FREEMEM(load_psp);
- X if (ercode != 0)
- X {
- X fprintf(stderr, msgs[0]);
- X fprintf(stderr, ermsg, s1, s2, s3, s4);
- X } /* if */
- X if (ercode == 1)
- X usage();
- X if (file != (FILE *) NULL)
- X fclose(file);
- X exit(ercode);
- X } /* error */
- X
- X/****************************************************************/
- X/* Brk_handler() catches CTRL-C interrupts. */
- X/****************************************************************/
- X
- Xstatic int brk_handler()
- X {
- X CTRLBREAK(brk_handler);
- X error(255,"User abort\n");
- X return(0); /* Actually not executed */
- X } /* brk_handler */
- X
- X/****************************************************************/
- X/* Tmr_handler() is the interrupt handler that updates hit */
- X/* counts. It must be fast. */
- X/****************************************************************/
- X
- X#if TRC_2_0
- Xstatic void interrupt tmr_handler(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs)
- X unsigned bp,di,si,ds,es,dx,cx,bx,ax,ip,cs;
- X#endif
- X#if MSC_5_1
- Xstatic void interrupt tmr_handler(es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs)
- X unsigned es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs;
- X#endif
- X {
- X long addr;
- X int lower, upper, middle;
- X
- X addr = ((unsigned long)cs << 4) + (unsigned long) ip;
- X lower = 0;
- X upper = nfuncs - 1;
- X
- X while (upper - lower > 1)
- X {
- X middle = (lower + upper) / 2;
- X if ((descs + middle)->addr <= addr)
- X lower = middle;
- X else
- X upper = middle;
- X } /* while */
- X (descs + lower)->hits++;
- X (*old_tmr_handler)();
- X } /* tmr_handler */
- END_OF_profile.c
- if test 23853 -ne `wc -c <profile.c`; then
- echo \"profile.c\" unpacked with wrong size!
- fi
- # end of overwriting check
- fi
- if test -f profile.man -a "${1}" != "-c" ; then
- echo Will not over-write existing file \"profile.man\"
- else
- echo Extracting - \"profile.man\"
- sed "s/^X//" >profile.man <<'END_OF_profile.man'
- XPROFILE(1) MS-DOS Programmer's Manual PROFILE(1)
- X
- XNAME
- X profile - execution time statistics collector
- X
- XSYNOPSIS
- X profile [-adin0] [-#<n>] [-m<mapfile>] [-o<outfile>] <cmdline>
- X
- XDESCRIPTION
- X Profile(1) executes a DOS command and gathers information about where in
- X the program the execution time is spent. After the command has terminated,
- X profile(1) writes a report file where the data is presented.
- X
- X Current implementations of profile(1) requires that the program under test
- X is linked with the MicroSoft LINK or the Borland TLINK linkers, and that a
- X standard format map file (as produced by the above linkers) was generated.
- X The link command for TLINK must contain the options '/l/c', and for the MS
- X LINK linker, the '/MAP' option must be specified.
- X
- X Normally, profile(1) assumes a linker map file with the same name as the
- X executable file (including any pathes) but with the extension '.MAP' in-
- X stead of '.EXE'. By default the output file has the same name as the '.EXE'
- X file, but with extension '.PRF'. Both these conventions can be overridden.
- X
- X The profiler will present it's output data summary based on the information
- X about public function names and addresses described by the linker map file.
- X If the map file is out of date, profiling results may be incorrect. Fur-
- X thermore, the profiler only displays public functions. One way to ensure
- X that interesting symbols in 'C' programs are public is to temporarily in-
- X sert the line
- X
- X #define static
- X
- X at the head of all interesting 'C' modules (this may in some cases cause
- X multiple definition errors in the link stage, if different functions and
- X variables have the same names in different modules).
- X
- X In cases where lengthy execution of a command is needed to produce relia-
- X ble statistics, an option is available to make the profiled command run
- X multiple times.
- X
- X Profile(1) will not process '.COM' files.
- X
- XINTERNAL FUNCTIONING
- X Profile(1) works by first reading the linker map file and collect all the
- X function name and address information in memory. It then find out where in
- X the physical memory the executable will be loaded, and adjusts table infor-
- X mation according to this. Next, it installs it's own handler function for
- X for the PC real-time clock interrupt, and loads and executes the program.
- X Each time a real-time clock interrupt occurs, the interrupt handler will
- X record in which function the CPU was executing. This continues until the
- X profiled program terminates. Profile(1) then uninstalls the interrupt
- X handler and formats and outputs the results.
- X
- X All functions described in the map file are classified either as 'intrinsic
- X functions' or 'user functions', according to their names: intrinsic func-
- X tions are those whose names start with '__', or which contain the charac-
- X ters '@' or '$'. All other functions are considered user functions. Apart
- X from the functions described in the map file, other interesting memory
- X areas are also defined:
- X
- X 'Low Mem' from 0 to 0x3ff
- X 'DOS' from 0x400 to the beginning od the profile itself
- X 'Profiler' from the beginning to the end of the profiler
- X 'EGA BIOS' from 0xc0000 to 0xc7fff
- X 'Fixed Disk BIOS' from 0xc8000 to 0xeffff
- X 'System ROM' from 0xf0000 to 0xfe000
- X 'System BIOS' from 0xfe000 to 0xfffff
- X
- XOUTPUT LISTING FORMAT
- X By default, DOS areas and intrinsic functions will not be listed in the
- X output report (this can be overridden). Below is an excerpt of a typical
- X report:
- X
- X Function name Addr Total Disp User
- X
- X _fclose 41b1c 0.32 50.00 50.00
- X _daylight 4a41e 0.00 3.00 3.00
- X _edata 4a826 0.00 3.00 3.00
- X _end 4ab60 0.00 3.00 3.00
- X _exit 416fa 0.00 2.00 2.00
- X _flushall 41c02 0.00 2.00 2.00
- X _fopen 41c46 0.00 1.00 1.00
- X _fprintf 41c7e 0.00 0.50 0.50
- X _main 402e0 0.00 0.50 0.50
- X ... ... ... ... ...
- X _timezone 4a41a 0.00 0.40 0.40
- X _tzname 4a420 0.00 0/30 0.30
- X FIARQQ 50112 0.00 0.10 0.10
- X FICRQQ 41112 0.00 0.01 0.01
- X
- X Statistics based on 309 hits
- X
- X As can be seen, functions are sorted according to their frequency of occu-
- X rence, and secondary by their name. They can also be sorted primarily ac-
- X cording to name or execution address. The first column gives name of the
- X function, and the second it's address at this particular instance of execu-
- X tion. Next is the percentage of each function related to total time, i.e.
- X if one adds up all DOS, intrinsic and user function percentages in this
- X column, the result will be 100. If DOS and intrinsics are not listed, the
- X total will be less than 100%.
- X
- X In the second column, the percentages relative to all DISPLAYED functions
- X are shown. In the third, the percentages relative to all USER functions
- X are listed. The two latter columns should always add up very closely to
- X 100% (the sum may not be exact due to rounding errors).
- X
- X Functions that did not get any hits are not displayed by default. However,
- X you can force them to appear in the output listing by giving the '-0'
- X option in the command line.
- X
- X In the last line is shown the total number of real-time clock interrupts
- X that occured during profiling. This value should be as large as possible,
- X otherwise it means the statistics is based on too little input data. See
- X the '#' option below to make multiple runs of the same command to increase
- X the statistical reliability of the data.
- X
- X In the example above, it can be seen that of the displayed functions, half
- X the time (50%) was spent in the _fclose function. Since _fclose is also
- X classified as a user function, the value is the same in the third column.
- X But only 0.32% of the total time was spent in _fclose, indicating that most
- X of the program's execution time is spent doing system calls into DOS or
- X other system resources. To find out, one could include DOS and intrinsic
- X functions in the output listing by using the '-d' and/or '-i' options.
- X
- XOPTIONS
- X -a Sort the output list in order of execution address, and not
- X by frequency.
- X -d Include DOS and BIOS areas, and the profiler itslef, in the
- X output list.
- X -i Include intrinsic functions in the output list.
- X -n Sort the output list in order of function name, and not by
- X frequency.
- X -0 Normally functions that did not get any hits during execution
- X are not listed in the output. The '-0' option causes them to
- X be listed.
- X -#<n> Execute the profiled command <n> times to increase the relia-
- X bility of the statistics (default 1).
- X -m<mapfile> Read the linker map from file <mapfile>. The default is the
- X same name as the executable, but with extension '.MAP'.
- X -o<outfile> Write output data to file <outfile>. The default is the same
- X name as the executable, but with extension '.OUT'.It is poss-
- X ible to write the data directly to 'CON' or to the printer
- X device.
- X <cmdline> The normal command line for the profiled command. Note that
- X profile(1) will not follow the DOS 'PATH' environment variable
- X to find the executable file. If you want to profile a command
- X that is in another directory, the explicit path to it must be
- X supplied. But the '.exe' extension should NOT be specified.
- X
- X Example:
- X
- X C> profile ..\tst\tester junk <cr>
- X
- X will profile the 'tester.exe' program in directory /..\tst'.
- X Note that in this example, the map file is assumed to be in
- X the '..\tst' directory too, and the profiler output file will
- X also be written to that directory.
- X
- XSPECIAL OPTIONS, NOT NORMALLY USED
- X -? Report the memory address of profile(1)'s own PSP and main()
- X function. Used only during development of profile(1).
- X -$ Report the memory address of the first free memory (approxi-
- X mately where the profiled command will be loaded). Used only
- X during development of profile(1). '-$' also causes more infor-
- X mation to be included in the output listing.
- X
- XDIAGNOSTICS
- X Illegal command line option: `x'
- X No command specified for profiling
- X Cannot find executable `x'
- X `x' is a directory
- X Cannot find linker map file `x'
- X Output file `x' is write-protected
- X Invalid map file format in `x'
- X Insufficient memory for function tables
- X Not enough function table slots
- X Cannot open output file `x'
- X Insufficient memory to execute command
- X Error executing command `x'
- X Invalid EXE file format in `x'
- X Program ran too fast - no hits!
- X
- XBUGS
- X The method for finding out the address where the profiled command will be
- X loaded is compiler specific (to the compiler used to compile profile(1)).
- X
- X It involves giving the command
- X
- X C> profile -$ profile -? <cr>
- X
- X These two (!) instances of profile will produce some special output. The
- X one which EXECUTES the other will show what memory address it thinks is the
- X lowest free one. The one which IS EXECUTED by the other will report the
- X address of it's own Program Segment Prefix and main() function. Naively,
- X the two values would be expected to be the same, but they are not.
- X
- X The discrepancy should be added during the adjustment of function addresses
- X to actual execution values. This is a nuisance. The values may even be dif-
- X ferent if you change the source code using the same compiler, so if you mo-
- X dify profile(1) you should check the address of the real executing main()
- X function and the assumed address for it and check that the values agree
- X after each change to the sources.
- X
- X It could be considered to have some kind of regular expression format, per-
- X haps read from a specification file, that tells what functions should be
- X included in or excluded from the output report.
- X
- X Other bugs? No doubt!
- X
- XNOTES
- X The writing of this command was inspired by a profiler distributed on Use-
- X Net by Diomidis Spinellis. Although his profile is very well written and
- X performs as described, it has two shortcomings. One is that the profiled
- X program has to be modified with a call to a special function that is linked
- X into it. Second, and much more serious: if you break out of the profiled
- X program with CTRL-C, the interrupt handler will not be restored, and as
- X soon as the memory used for it is overwritten by another command, the PC
- X dies immediately.
- X
- X This profile(1) does noit suffer from these problems (but possibly from
- X many others...). Although I have taken some ideas from Diomidis' version,
- X most of this one is original work. It is hereby placed in the public do-
- X main.
- X
- X Author: Bjorn Larsson uucp: bl@infovox.se
- X Ynglingagatan 5, IV
- X S-113 47 Stockholm
- X SWEDEN
- END_OF_profile.man
- if test 11010 -ne `wc -c <profile.man`; then
- echo \"profile.man\" unpacked with wrong size!
- fi
- # end of overwriting check
- fi
- if test -f README -a "${1}" != "-c" ; then
- echo Will not over-write existing file \"README\"
- else
- echo Extracting - \"README\"
- sed "s/^X//" >README <<'END_OF_README'
- X PROFILER READ-ME
- X ================
- X
- XThis software is an execution time profiler for programs developed
- Xwith Turbo C and Microsoft C, or any other program that is linked
- Xwith Microsoft or Turbo LINK. PROFILE will read the linker's output
- Xmap file, and then run the executable program N times (default 1),
- Xusing the timer interrupt to gather statistical data about where
- Xexecution is mostly spent. After the executable has been run, the
- Xdata is formatted in different ways and sent to a file.
- X
- XAs is mentioned in the MAN file, there is a constant (ADJ_CONST) in
- Xthe source that is dependent on the compiler used. This is a correc-
- Xtion constant to compensate the actual load address for the executing
- Xprogram when it resides in primary memory. The supplied values work
- Xfor MicroSoft C v.5.1, and for TurboC v.2.0. Probably other values
- Xneed to be used for other compilers, and maybe also for other ver-
- Xsions of DOS (the supplied values work for PCDOS 3.30). Unfortunately,
- XI know of no way to know for certain where a program executed by
- Xspawn..() will come down into memory. On the other hand PROFILE con-
- Xtains two command line switches ('-?' and '-#') to check that the
- Xconstant is correct. In other words, to install this you need a
- Xlittle more than just compile it and go... it works well in my
- Xinstallation. Though I'd pass it along - hopefully it is of use
- Xto at least someone.
- X
- XA word of the program's heritage - I started to write it after
- Xcollecting Diomidis Spinellis' profiler for UseNet. Although his
- Xprofiler works as advertised (and is well written) I think my
- Xversion is somewhat improved, as discussed in the MAN file. At
- Xleast, it will handle CTRL-C in an orderly manner...
- X
- XTo compile, you need a UNIX compatible make (in other words,
- XMicroSoft MAKE will NOT work). Anyway the makefiles are so simple
- Xyou could just as well compile 'by hand'. Who knows, maybe some
- Xof you can remove the need of AJD_CONST and post the result (or
- Xat least mail it to me)?
- X
- X Bjorn
- END_OF_README
- if test 1992 -ne `wc -c <README`; then
- echo \"README\" unpacked with wrong size!
- fi
- # end of overwriting check
- fi
- echo End of shell archive.
- ## End of shell archive.
- exit 0
-
- ====================== InfoVox = Speech Technology =======================
- Bjorn Larsson, INFOVOX AB : ...seismo!mcvax!kth!sunic!infovax!bl
- Box 2503 : bl@infovox.se
- S-171 02 Solna, Sweden : Phone (+46) 8 735 80 90
-
-